桌面程序绘图编程(GDI) – Windows API

最近更新于 2024-05-05 14:18

环境

Windows 11 专业工作站版 22H2
Visual Studio 2022 专业版

Unicode 字符集,C17 标准编译,64 位编译

像素点及简单图形绘制

#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps = {0};
            HDC hdc = BeginPaint(hWnd, &ps);
            for (int i = 50; i < 200; ++i) // 连续绘制点构成一条直线
            {
                // 绘制像素点
                // RGB 三色配比的取值范围 0~255
                SetPixel(hdc, i, i, RGB(i + 50, i - 10, i + 50));
            }
            // 直线绘制
            // 用四条线首尾相连画一个矩形
            MoveToEx(hdc, 20, 20, NULL); // 起始位置,不指定 LineTo 默认从 (0,0) 开始
            LineTo(hdc, 20, 200);
            LineTo(hdc, 200, 200); // 上一次的末尾作为起始点
            LineTo(hdc, 200, 20);
            LineTo(hdc, 20, 20);
            // 矩形绘制
            Rectangle(hdc, 30, 30, 240, 100);  // 封闭图形,内部有填充颜色,默认白色,和背景一样,会遮挡前面绘制的图案。下同
            // 圆/椭圆绘制
            Ellipse(hdc, 35, 35, 90, 90); // 指定圆或椭圆的外接矩形的左上角和右下角坐标
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_CLOSE:
        {
            DestroyWindow(hWnd);
            break;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }
        default:
        {
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
    }

    return 0;
}

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    PCWSTR szClassName = L"Desktop";
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = szClassName;
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"注册窗口失败!", L"错误", MB_OK);
        return 1;
    }

    HWND hWnd = CreateWindowEx(0, szClassName, L"简单程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
        MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
        return 1;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

file

画笔和画刷的使用

相比于前面的绘图,还可以设置画笔的粗细以及颜色,另外还有图形填充。

#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps = {0};
            HDC hdc = BeginPaint(hWnd, &ps);
            HPEN hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); // 创建画笔,第一个参数设置线型,PS_SOLID 是实线,比如 PS_DASH 是虚线,还有其它线型,如果第二个参数线宽大于 1,那么还是会变成实线
            // 这里是创建纯色的画刷
            // 还有可以创建纹理的画刷等等,函数名格式大概就是 Create[XXXX]Brush
            // 画刷用于填充
            HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 255));
            HGDIOBJ hOldPen = SelectObject(hdc, hPen); // 将画笔切换为自己创建的,并保存返回值,即默认画笔
            HGDIOBJ hOldBrush = SelectObject(hdc, hBrush); // 将画刷切换为自己创建的,并保存返回值,即默认画刷
            Rectangle(hdc, 20, 20, 250, 100); // 绘图
            SelectObject(hdc, hOldPen); // 将画笔重新设为默认的
            SelectObject(hdc, hOldBrush); // 将画刷重新设为默认的
            DeleteObject(hPen); // 销毁创建的画笔
            DeleteObject(hBrush); // 销毁创建的画刷
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_CLOSE:
        {
            DestroyWindow(hWnd);
            break;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            break;
        }
        default:
        {
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
    }

    return 0;
}

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    PCWSTR szClassName = L"Desktop";
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = szClassName;
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"注册窗口失败!", L"错误", MB_OK);
        return 1;
    }

    HWND hWnd = CreateWindowEx(0, szClassName, L"简单程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
        MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
        return 1;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

file

位图的使用

先创建一个资源文件,再添加位图资源

file
file

然后可以自己绘制图案,在空白处右键属性可以设置位图大小,我这里就是默认的 48×48(后面写代码要用)
file
画完 Ctrl+S 保存一下,开始撸代码
file

顺便提一下,位图分为光栅图形和矢量图形,这里画的属于前者,图像数据会记录每个像素点,而矢量图形通常记录的图像的算法及绘图指令等,就比如我图像上要展示 y=x^2 的图像,我并不需要画出来再保存这个图像,而是保存这个函数式。那么矢量图形就有一个性质,放大图像都不会影响清晰度,因为图像内容是根据绘图的指令直接生成的,图像和分辨率没什么关系。

#include <Windows.h>
#include "resource.h" // 引入资源文件

HBITMAP g_hBitmap = {0}; // 用于保存位图资源句柄

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps = {0};
            HDC hdc = BeginPaint(hWnd, &ps);
            HDC hdc_ccdc = CreateCompatibleDC(hdc); // 创建和 hdc 兼容的内存上下文
            HGDIOBJ hOldBitmap = SelectObject(hdc_ccdc, g_hBitmap); // 将位图资源放入 hdc_ccdc
            // 将 hdc_ccdc 中的位图资源原样复制到 hdc 中,即实现位图显示出来
            // 目的 DC 句柄
            // 目的左上角 x
            // 目的左上角 y
            // 目的宽度 - 这里就对应的位图的分辨率 48x48,根据自己绘制的图像大小而定
            // 目的高度
            // 源 DC 句柄
            // 源的左上角 x
            // 源的左上角 y
            // 操作代码,这里是原样复制
            BitBlt(hdc, 30, 30, 48, 48, hdc_ccdc, 0, 0, SRCCOPY); 
            SelectObject(hdc_ccdc, hOldBitmap);
            DeleteDC(hdc_ccdc);
            EndPaint(hWnd, &ps);
            break;
        }
        case WM_CLOSE:
        {
            DestroyWindow(hWnd);
            break;
        }
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            DeleteObject(g_hBitmap); // 释放位图资源
            break;
        }
        default:
        {
            return DefWindowProc(hWnd, msg, wParam, lParam);
        }
    }

    return 0;
}

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPreInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
    g_hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)); // 加载位图资源

    PCWSTR szClassName = L"Desktop";
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = szClassName;
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"注册窗口失败!", L"错误", MB_OK);
        return 1;
    }

    HWND hWnd = CreateWindowEx(0, szClassName, L"简单程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
        MessageBox(NULL, L"创建窗口失败!", L"错误", MB_OK);
        return 1;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg = {0};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

file

桌面程序绘图编程(GDI) – Windows API
Scroll to top